package utils

import (
	"errors"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v4"
	"github.com/spf13/viper"
	"time"
)

var (
	TokenExpired     = errors.New("token过期了")
	TokenNotValidYet = errors.New("token不能为空")
	TokenMalformed   = errors.New("token令牌错误")
	TokenInvalid     = errors.New("token不合法")
)

type TokenUser struct {
	Name string `json:"name"`
	Id   uint   `json:"id"`
}
type TokenClaims struct {
	TokenUser
	jwt.RegisteredClaims
}

// GenerateToken 生成token
func GenerateToken(tokenUser TokenUser) (string, error) {
	tokenClaims := &TokenClaims{
		TokenUser: tokenUser,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			Subject:   viper.GetString("Jwt.TokenSubject"),
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, tokenClaims)
	sign, err := token.SignedString([]byte(viper.GetString("Jwt.TokenSecret")))
	return sign, err
}

// ParseToken 校验token
func ParseToken(Token string) (*TokenClaims, error) {
	token, err := jwt.ParseWithClaims(Token, &TokenClaims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(viper.GetString("Jwt.TokenSecret")), nil // 签名是 []byte
	})
	if err != nil {
		var ve *jwt.ValidationError
		if errors.As(err, &ve) {
			if ve.Errors&jwt.ValidationErrorMalformed != 0 {
				return nil, TokenMalformed
			} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
				return nil, TokenExpired
			} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
				return nil, TokenNotValidYet
			} else {
				return nil, TokenInvalid
			}
		}
	}
	if token != nil {
		if claims, ok := token.Claims.(*TokenClaims); ok && token.Valid {
			return claims, nil
		}
		return nil, TokenInvalid

	} else {
		return nil, TokenInvalid
	}
}

// GetToken 传入token返回解析之后的token
func GetToken(c *gin.Context) (*TokenClaims, error) {
	token := c.Request.Header.Get("x-token")
	if token == "" {
		return nil, TokenNotValidYet
	}
	claims, err := ParseToken(token)
	if err != nil {
		return nil, err
	}
	return claims, nil
}

// GetUserId 认证上下文获取用户Id
func GetUserId(c *gin.Context) uint {
	if claims, exists := c.Get("claims"); !exists {
		return 0
	} else {
		waitUse, _ := claims.(*TokenClaims)
		return waitUse.TokenUser.Id
	}
}
